package com.fuda.uifinder

import android.annotation.SuppressLint
import android.graphics.Rect
import android.view.accessibility.AccessibilityNodeInfo
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json

@Serializable
data class Node(
    val index: Int? = null,
    val viewIdResourceName: String?,
    val className: String?,
    val text: String?,
    val children: List<Node> = emptyList(),
    val path:String,
    val boundsInScreen: String,
    val isClickable: Boolean, 
    val isLongClickable:Boolean
) {
    companion object {
        @SuppressLint("DefaultLocale")
        // if parent is Root
        fun fromAccessibilityNodeInfo(parent: AccessibilityNodeInfo, pi: String, index: Int): Node {
            val viewIdResourceName = parent.viewIdResourceName?.toString()
            val className = parent.className?.toString()
            val text = parent.text?.toString()
            val path =  if (index >=0) {String.format("$pi$index,")}else{""}
            val children = mutableListOf<Node>()
            for (i in 0 until parent.childCount) {
                val childNodeInfo = parent.getChild(i)
                if (childNodeInfo==null){
                    children.add(Node(i,"null","null","null", emptyList(), String.format("$pi,$i"), "",false,false ))
                }else {
                    children.add(fromAccessibilityNodeInfo(childNodeInfo,path, i))
                }
            }

            val isClickable = parent.isClickable
            val isLongClickable = parent.isLongClickable
            val boundsInScreen = Rect()
            parent.getBoundsInScreen(boundsInScreen)
            val boundsInScreenStr = String.format( "[%d, %d, %d, %d]", boundsInScreen.left, boundsInScreen.top, boundsInScreen.right, boundsInScreen.bottom )
            return Node(
                index = index,
                viewIdResourceName = viewIdResourceName,
                className = className,
                text = text,
                children = children,
                path = path,
                boundsInScreen =boundsInScreenStr,
                isClickable= isClickable,
                isLongClickable = isLongClickable 
            )
        }
        // Usage example:
        // Assuming you have an AccessibilityNodeInfo called 'rootNodeInfo'
        //        val rootNode = Node.fromAccessibilityNodeInfo(rootNodeInfo, 0)
        //        val indentationString = buildIndentationString(rootNode)
        fun buildIndentationString(node: Node, indent: String = ""): String {
            // Use StringBuilder to construct the nodeInfo string
            val nodeInfo = StringBuilder().apply {
                // Append the node's information with the current indent
                append("$indent${node.index}") // Using orEmpty to handle nulls
                append(", ")
                append(node.viewIdResourceName)
                append(", ")
                append(node.className)
                append(", ")
                append(node.text?.replace("\n","")
                    ?.let { String.format("[%s]", it.replace("\r","")) }
                ) // Handle null text with an empty string
                .append(", ")
                .append(String.format("%s, ",node.path))
                .append(String.format("%s, ",node.boundsInScreen))
                .append(String.format("%s, ",node.isClickable))
                .append(String.format("%s, ",node.isLongClickable))

                // Add a newline character for separation
                append("\n")
            }

            // Define the indentation for children with two additional underscores
            val childIndent = "${indent}__"

            // Recursively build the string for children, increasing the indentation
            node.children.forEach { childNode ->
                // Convert the result of the recursive call to a String and append it
                nodeInfo.append(buildIndentationString(childNode, childIndent))
            }

            // Return the final string representation of the node and its children
            return nodeInfo.toString()
        }

        // Helper method to find the view by path
        // Define the path as an array of indices
        // val path = intArrayOf( 0, 1 )
        // For example, the 1st child of the root, followed by the 2nd child of that node
        fun findChildNodeByPath(
            root: AccessibilityNodeInfo?,
            path: IntArray?
        ): AccessibilityNodeInfo? {
            if (root == null || path == null || path.isEmpty()) {
                return null
            }

            var currentNode = root
            for (index in path) {
                if (currentNode == null || index < 0 || index >= currentNode.childCount) {
                    return null
                }

                currentNode = currentNode.getChild(index)
            }

            return currentNode
        }

    }

    fun serializeToJson(): String {
        val serializer = Json {
            prettyPrint = true
            isLenient = true
            ignoreUnknownKeys = true
        }
        return serializer.encodeToString(this)
    }
}

// Usage example:
// Assuming you have an AccessibilityNodeInfo called 'rootNodeInfo'
//val rootNode = Node.fromAccessibilityNodeInfo(rootNodeInfo, 0)
//val jsonString = rootNode.serializeToJson()